/*
	main.c: main file of the kernel module
    Copyright (C) 2012  Adrian Remonda,
                        Luis Barrueco
                        FuDePAN
    This file is part of the reclamamem Proyect.

    Reclamamem is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Reclamamem is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Reclamamem.  If not, see <http://www.gnu.org/licenses/>.
*/
#include <linux/kernel.h>   /* We're doing kernel work */
#include <linux/module.h>   /* Specifically, a module */
#include <linux/fs.h>
#include <linux/types.h>    /* For boolean type */
#include <asm/uaccess.h>    /* for get_user and put_user */
#include <linux/init.h>
#include "reclamamem.h"

#define SUCCESS 0
#define DEVICE_NAME "reclamamem"
#define DRIVER_AUTHOR "Fudepan"
#define DRIVER_DESC   "Part of the reclamamem project"

#define BUF_LEN 80

MODULE_LICENSE("GPL");
MODULE_AUTHOR(DRIVER_AUTHOR);   /* Who wrote this module? */
MODULE_DESCRIPTION(DRIVER_DESC);    /* What does this module do */

/*
 * Used to prevent concurent access into the same device
 */
static bool Device_Open = false;

/*
 * The message the device will give when asked
 */
static char Message[BUF_LEN] = "Hi from Fudepan";
static char* Message_Ptr;


int device_open(struct inode* inode, struct file* filp)
{
    printk(KERN_ALERT "[RECLAMAMEM] Open\n");

    if (Device_Open)
    {
        printk(KERN_ALERT "[RECLAMAMEM] Device is busy\n");
        return -EBUSY;
    }

    Device_Open = true;

    /*
     * Initialize the message
     */
    Message_Ptr = Message;

    return SUCCESS;
}

int device_release(struct inode* inode, struct file* file)
{
    printk(KERN_ALERT "[RECLAMAMEM] module release (close)\n");
    Device_Open = false;
    return 0;
}

ssize_t device_read(struct file* filp,
                    char* buffer,                    /* buffer to fill with data */
                    size_t length,                   /* length of the buffer     */
                    loff_t* offset)
{
    /*
     * Number of bytes actually written to the buffer
     */
    ssize_t bytes_read = 0;

    printk(KERN_ALERT "[RECLAMAMEM] read\n");

    printk(KERN_INFO "device_read(%p,%p,%d)\n", filp, buffer, (int)length);

    /*
     * Until we get to the end off the string
     */
    if (*Message_Ptr == 0)
        return 0;

    /* copy from kernel to user space */
    while (length > 0 && *Message_Ptr != 0)
    {
        put_user(*(Message_Ptr++), buffer++);
        length--;
        bytes_read++;
    }

    return bytes_read;
}

/*
 * Called when a process writes to dev file: echo "hi" > reclamamem
 */
ssize_t device_write(struct file* filp,
                     const char* buffer,
                     size_t length,
                     loff_t* offset)
{
    printk(KERN_ALERT "[RECLAMAMEM] Wirte is still not supported.\n");
    return -EINVAL;
}

long int device_ioctl(struct file* file,
                 unsigned int ioctl_num,
                 unsigned long ioctl_param)
{
    long int ret_val = 0;

    /*
     * Switch according to the ioctl called
     */
    switch (ioctl_num)
    {
        case IOCTL_TO_DEFINE_1:
            printk(KERN_ALERT "[RECLAMAMEM] IOCTL_TO_DEFINE_1.\n");
            ret_val = 0xfa;
            break;

        case IOCTL_TO_DEFINE_2:
            printk(KERN_ALERT "[RECLAMAMEM] IOCTL_TO_DEFINE_2.\n");
            ret_val = 0xa5;
            break;
        default:
            ret_val = -EINVAL;
    }

    return ret_val;
}

const struct file_operations Fops =
{
    .read = device_read,
    .write = device_write,
    .unlocked_ioctl = device_ioctl,
    .open = device_open,
    .release = device_release,  /* close */
};

/*
 * Initialize the module
 */
static int reclam_init_module(void)
{
    int ret_val;

    /*
     * Register the character device
     */
    ret_val = register_chrdev(101, DEVICE_NAME, &Fops);
    if (ret_val < 0)
    {
        printk(KERN_ALERT "%s failed with %d\n",
               "Sorry, registering the reclamamem module", ret_val);
        return ret_val;
    }

    printk(KERN_ALERT "============[RECLAMAMEM]========\n");
    printk(KERN_ALERT "registering reclamamem module...\n");

    printk(KERN_INFO "%s The major device number is %d.\n",
           "Registeration is a success", MAJOR_NUM);
    return 0;
}

/*
 * Cleanup
 */
static void reclam_cleanup_module(void)
{
    printk(KERN_ALERT "[RECLAMAMEM] module clean up\n");

    /*
     * Unregister the device
     */
    unregister_chrdev(MAJOR_NUM, DEVICE_NAME);
}

module_init(reclam_init_module);
module_exit(reclam_cleanup_module);
